package org.splink.library.logging.logoutput.qlogutils {
	import org.splink.library.logging.ILogger;
	import org.splink.library.logging.LogLevel;
	import org.splink.library.logging.LoggerProvider;
	import org.splink.library.logging.logoutput.QLogOutput;
	import org.splink.library.reflection.ClazzInspector;
	import org.splink.library.reflection.IMember;
	import org.splink.library.reflection.Method;
	import org.splink.library.tree.INode;
	import org.splink.library.tree.Node;
	import org.splink.library.tree.Tree;

	import flash.display.DisplayObject;
	import flash.utils.Dictionary;

	/**
	 * Recurses through the structure of any object and stores the object including retrievable values
	 * within a <code>Tree</code> which is serialized into xml and sent to QLog. QLog itself opens 
	 * a new tab or refreshes an already opened tab with the same name and visualizes the xml as a 
	 * navigatable tree.
	 * 
	 * Note that only public methods and public variables are considered which do not return void and 
	 * which do not require arguments (methods that is)
	 * Also note that <code>ObjectTree</code> cant process <code>DisplayObject</code>s.
	 * <code>ObjectTree</code> probably works best with value objects as it only considers methods,
	 * vars & accessors that return values.
	 * 
	 * @author Max Kugland
	 */
	public class ObjectTree {
		private var _logger : ILogger;
		private var _obj : Object;
		private var _tree : Tree;
		private var _tabName : String;

		/**
		 * @param obj any Object you wish to inspect. Note that only methods and variables which are
		 * declared as public, have not parameters and don't return void are considered
		 * @param logFactoryId the id for the <code>ILoggerFactory</code> you wish to use to send
		 * the data to QLog
		 * @param tabName the name of the tab in QLog
		 */
		public function ObjectTree(obj : Object, logFactoryId : String, tabName : String = "ObjectTree") {
			_obj = obj;
			_tabName = tabName;
			_logger = LoggerProvider.getLogger(logFactoryId, ObjectTree);

			if(obj is DisplayObject) {
				_logger.log(LogLevel.ERROR, "Cant process DisplayObjects");
				return;
			}

			_tree = new Tree(null);
			recurse(null, _obj);

			QLogOutput(_logger.getLogOutput(QLogOutput)).sendXmlTree(Tree.toXml(_tree, _tree.root.id, null), _tabName);
		}

		private function recurse(parent : INode, parentObj : Object) : void {
			var inspector : ClazzInspector = ClazzInspector.inspectClazzByInstance(parentObj);

			var classname : String = inspector.clazzname;
			if(classname.indexOf("::") != -1)
				classname = classname.split("::")[1];

			var clazzNode : INode = new Node(classname, parent);
			if(parent == null)
				parent = _tree.root = clazzNode;
			else
				parent = parent.add(clazzNode);

			// untyped object
			if(inspector.clazzname == "Object") {
				for (var o : String in parentObj) {
					var obj : * = parentObj[o];
					if(checkNativeTypes(obj, parent))
						continue;
					if(obj is Object) {
						recurse(parent, obj);
					}
				}
				return;
			}

			var ar : Array = new Array;
			if(!(parentObj is Array)) {
				ar = ar.concat(inspector.constants);
				ar = ar.concat(inspector.methods);
				ar = ar.concat(inspector.accessors);
				ar = ar.concat(inspector.variables);
				ar = ar.concat(inspector.staticConstants);
				ar = ar.concat(inspector.staticVariables);
				ar = ar.concat(inspector.staticMethods);
				ar = ar.concat(inspector.staticAccessors);
			}

			for each (var m : IMember in ar) {
				if(m is Method && (Method(m).params.length > 0))
					continue;
				if(m.returntype.indexOf("void") != -1)
					continue;
				if((m.name == "prototype"))
					continue;

				var type : String = m.returntype;
				if(type.indexOf(".") != -1 || type.indexOf("::") != -1)
					type = type.split("::")[1];

				var node : INode = new Node("ı" + m.name + "-" + type, parent);
				parent.add(node);

				var content : * = null;
				try {
					content = m.invoke(parentObj);
				} catch(e : Error) {
				}
				if(content) {
					if(checkNativeTypes(content, node)) {
						continue;
					}
					if(content is Array) {
						var l : int = content["length"];
						for (var i : int = 0;i < l; i++) {
							var arrayContent : * = content[i];
							if(checkNativeTypes(arrayContent, node)) {
								continue;
							}
							if(arrayContent is Object) {
								recurse(node, arrayContent);
							}
						}
						continue;
					}
					if(content is Dictionary) {
						for (var dictContent:* in content) {
							if(checkNativeTypes(dictContent, node)) {
								continue;
							}
							if(dictContent is Object) {
								recurse(node, dictContent);
							}
						}
						continue;
					}

					recurse(node, content);
				}
			}
		}

		private function checkNativeTypes(content : *, node : INode) : Boolean {
			var isNative : Boolean = true;
			if(content is String) {
				content = String(content).replace(new RegExp("[^a-zA-Z-0-9]", "g"), "");
				node.add(new Node("ı" + content, node));
			} else if(content is Number)
				node.add(new Node("ı" + content, node));
			else if(content is Date)
				node.add(new Node("ı" + (content as Date).time, node));
			else
				isNative = false;
			return isNative;
		}
	}
}
